SPDX-FileCopyrightText: 2014 Nitay Lehrer SPDX-FileCopyrightText: 2024 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
HABITAT 67’ script - Nitay Lehrer - 22/01/2015 Created in blender hash 9337574, OS: Microsoft Windows 7.0
For script to work, PIVOT CENTER needs to be set on “MEDIAN POINT” on both OBJECT and EDIT modes# All PRINT commands are for debugging purposes, and their output is erased before the final output. To enable PRINT outputs, place “#” before os.system(“cls”) , at the very end of the script.
import bpy
import random
import bpy, bmesh
from mathutils import Vector
import osErasing all previously generated objects
bpy.context.scene.layers[0] = False
bpy.context.scene.layers[19] = True
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.context.scene.layers[0] = True
bpy.context.scene.layers[19] = False
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)        #---------------------------------------------------------------#
        #                   MAKING THE RANDOM PERIMITER                 #
        #---------------------------------------------------------------#
    def myBox(pos_X, pos_Y, pos_Z, dim_X, dim_Y, dim_Z):
    bpy.ops.mesh.primitive_cube_add(location=(pos_X, pos_Y, pos_Z))
    bpy.ops.transform.resize(value=(0.5, 0.5, 0.5))
    bpy.ops.transform.resize(value=(dim_X, dim_Y, dim_Z))
array_X = random.randint(1, 3)
array_Y = random.randint(1, 3)
for j in range(0, array_Y):
    for i in range(0, array_X):
        scale = random.randint(1, 5)
        myBox(i, j, -0.02, random.uniform(0.5, 3), random.uniform(1, 2), 0.01)bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.join()  # joining the boxes into one geometry
bpy.context.object.name = "BASE"  # Changing the name
bpy.context.object.data.name = "BASE"
bpy.data.objects["BASE"].bound_box
bpy.ops.object.origin_set(
    type="ORIGIN_CENTER_OF_MASS"
)  # changing the origin of the base plane to the center of mass
bpy.context.object.location[0] = 0  # centering the base plane in the X Y axis
bpy.context.object.location[1] = 0
bpy.context.object.location[2] = -0.01        #--------------------------------------------------------------------#
        #                      DEFINING ALL FUNCTIONS                        #
        #--------------------------------------------------------------------#
CREATING ALL THE LISTS WE WILL USE IN THE SCRIPT:
              stack = All the modules used in the process
      lastDirection = The last written direction of the module
      nextDirection = Once reaching an edge, the next module direction according to the orientation of the edge
              elbow = The next elbow to be placed according to the last and next directions
        placedElbow = Verification that the last placed elbow is the correct one
               cube = The bounding box of the current module
     iterationCount = A registry of the amount of times a module has been replicated in a wing
     coorObjectMode = Coordinates of the center of the bounding box in object mode, used in edgeAnalysis()
       coorEditMode = Coordinates of the center of the bounding box in edit mode, also used in edgeAnalysis()
   edgeAnalysisList = A registry of the last orientations of an edge hit by a wing - either "Ver"(tical) or "Hor"(izontal)
    stack = []
lastDirection = []
nextDirection = []
elbow = []
placedElbow = []
cube = []
iterationCount = []
coorObjectMode = []
coorEditMode = []
edgeAnalysisList = []THE FUNCTION THAT STARTS THE PROCESS DebName = The predefined name of the start module to look for in layer 1. The name corresponds to the beginning direction. Modname = The predefined name of the first drectional module to look for in layer 2. The name corresponds to the beginning direction. Direction = The Random beginning direction — NW= North-West NE= North-East SE= South-East SW= South-West trans_X = The X manipulation of the module in the chosen direction trans_Y = The Y manipulation of the module in the chosen direction
def START(DebName, ModName, Direction, trans_X, trans_Y):    bpy.ops.object.select_all(action="TOGGLE")
    bpy.context.scene.layers[1] = True
    object = bpy.data.objects[DebName]
    object.select = True
    bpy.context.scene.objects.active = bpy.data.objects[DebName]
    bpy.ops.object.duplicate()
    bpy.context.scene.objects.active = bpy.data.objects[DebName + ".001"]Moving the start module into position in layer 0 and turning off layer 1. Changing the name to correspond to the direction.
    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[1] = False
    bpy.context.object.name = "Deb" + str(Direction)
    bpy.context.object.data.name = "Deb" + str(Direction)
    bpy.ops.object.select_all(action="TOGGLE")Moving the first directional module into position in layer 0 and turning off layer 2. Changing the name to correspond to the direction.
    bpy.context.scene.layers[2] = True
    object2 = bpy.data.objects[ModName]
    object2.select = True
    bpy.context.scene.objects.active = bpy.data.objects[ModName]
    bpy.ops.object.duplicate()
    bpy.context.scene.objects.active = bpy.data.objects[ModName + ".001"]
    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[2] = False
    bpy.context.object.name = "MOD" + str(Direction)
    bpy.context.object.data.name = "MOD" + str(Direction)    bpy.context.active_object.location += Vector([trans_X, trans_Y, 0])
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    stack.append(
        bpy.context.scene.objects.active
    )  # Adding the new module as the first in the list of directional modules ( stack[] )BUILDING THE BOUNDING BOX OF EACH MODULE, THEN INTERSECTING IT WITH THE BASE PLANE. THIS WILL TELL US IF THE MODULE HASE REACHED AN EDGE. According to the direction of the wing and the beginning position of the process, the box will be moved differently. DirectionInt = An integer corresponding to the random initial direction. 0=NW 1=NE 2=SE 3=SW translateBox = The set of vector coordinates that wil govern the displacement of the box, so that it perfectly encompasses the module.
def boxInter2(DirectionInt):    if (startingPos == 0 or startingPos == 2) and DirectionInt == 0:
        translateBox = (0.02, -0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 0:
        translateBox = (-0.02, 0.2264, 0)
    elif (startingPos == 0 or startingPos == 2) and DirectionInt == 1:
        translateBox = (0.02, 0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 1:
        translateBox = (-0.02, -0.2264, 0)
    elif (startingPos == 0 or startingPos == 2) and DirectionInt == 2:
        translateBox = (-0.02, 0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 2:
        translateBox = (0.02, -0.2264, 0)
    elif (startingPos == 0 or startingPos == 2) and DirectionInt == 3:
        translateBox = (-0.02, -0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 3:
        translateBox = (0.02, 0.2264, 0)    bpy.ops.mesh.primitive_cube_add(
        view_align=False,
        enter_editmode=False,
        location=(0, 0, 0),
    )
    bpy.ops.transform.resize(value=(0.1265, 0.2265, 0.5))
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_selected_to_cursor(
        use_offset=False
    )  # displacing the center of the box to the cursor
    bpy.context.area.type = "TEXT_EDITOR"
    bpy.ops.transform.translate(
        value=translateBox
    )  # final displacement of the box to fit to module    bpy.ops.object.modifier_add(
        type="BOOLEAN"
    )  # Boolean difference operation on base plane
    bpy.context.object.modifiers["Boolean"].operation = "DIFFERENCE"
    bpy.context.object.modifiers["Boolean"].object = bpy.data.objects["BASE"]
    bpy.ops.object.modifier_apply(
        apply_as="DATA", modifier="Boolean"
    )  # applying the boolean to create a hard coded form
    bpy.ops.object.modifier_add(
        type="DECIMATE"
    )  # "Decimate" modifier on the remaining geometry
    bpy.context.object.modifiers["Decimate"].decimate_type = (
        "DISSOLVE"  # This merges all coplanar faces, so as not to give false readings in the face count
    )
    bpy.ops.object.modifier_apply(apply_as="DATA", modifier="Decimate")
    result = bpy.context.object
    count = len(result.data.polygons)  # Counting the amount of faces
    edges = len(result.data.edges)  # Counting the amount of vertices
    cube.append(
        bpy.context.scene.objects.active
    )  # Adding the cube to the list of cubes
    print("___1___the number of faces is", str(count))
    print("___2___the number of edges is", str(edges))
    print("___3___the last cube is", cube[-1])Only a box that is perfectly on top of the base plane will have 12 FACES and 24 VERTICES, anything else means that the box has reached or transgressed an edge.
CREATING A NEW WING OF MODULES
Based on the first directional module, the function copies it until it reaches the edge of the island.
  Direction = The required direction of the wing
    trans_X = Translation of the copied modules in the X axis
    trans_Y = Translation of the modules on the Y axis
DirectionInt = The direction integer fed into the boxInter2() function x = The maximum number of modules in a wing
def wing(Direction, trans_X, trans_Y, DirectionInt):
    m = 0
    x = 12
    while m <= x:
        print(
            "___4___the last object in stack before wing in iteration",
            m + 1,
            "is",
            stack[-1],
        )Analysing the intersected box, left over from boxInter2(), to discover the orientation of the reached edge.
        def edgeAnalysisForWing():
            A = (
                bpy.context.object.location
            )  # geometric coordinates of the center of the box
            bpy.ops.object.editmode_toggle()  # going into edit mode, which moves the center of gravity
            bpy.context.area.type = "VIEW_3D"
            bpy.ops.view3d.snap_cursor_to_selected()  # moving cursor to new center of the object.
            bpy.context.area.type = "TEXT_EDITOR"
            B = (
                bpy.context.scene.cursor_location
            )  # retreiving the coordinates of the cursor
            bpy.ops.object.editmode_toggle()
            coorObjectMode.append(A)
            coorEditMode.append(B)
            print(A)
            print(B)
            A = [abs(i) for i in A]  # absolute value of coordinates
            B = [abs(i) for i in B]
            C = [b - a for a, b in zip(A, B)]  # Subtracting one vector from the other
            C = [abs(i) for i in C]
            print(C)
            if C[0] > C[1]:  # if the result in X is bigger than in Y, the edge is N/S
                edgeAnalysisList.append("Ver")
                print("___5___the edge is vertical")
            else:  # if the result is bigger in Y than in X, the edge is E/W
                edgeAnalysisList.append("Hor")
                print("___5___the edge is horizontal")All necessary action before reiterating Wing() : First, the bounding box is stocked in layer 19, for debugging purposes. Then, the last module is selected again, to be duplicated in the next iteration
        def nextIteration():
            bpy.ops.object.move_to_layer(
                layers=(
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    True,
                )
            )
            l = [False] * 20
            l[0] = True
            bpy.context.scene.layers = l
            bpy.context.scene.layers[19] = False
            bpy.ops.object.select_all(action="SELECT")
            bpy.ops.object.select_all(action="TOGGLE")
            print("___6___the last object in stack2 is", stack[-1])
            stack[-1].select = True
            bpy.context.scene.objects.active = stack[-1]
            print("___7___the last object is", bpy.context.scene.objects.active)        if DirectionInt == 0:
            lastDirection.append("NW")
        elif DirectionInt == 1:
            lastDirection.append("NE")
        elif DirectionInt == 2:
            lastDirection.append("SE")
        elif DirectionInt == 3:
            lastDirection.append("SW")        bpy.ops.object.duplicate_move(
            OBJECT_OT_duplicate={"mode": "TRANSLATION"},
            TRANSFORM_OT_translate={"value": (trans_X, trans_Y, 0)},
        )
        stack.append(
            bpy.context.scene.objects.active
        )  # appending the module to the list
        print(
            "___8___--THE NEXT OBJECT IN THE WING IS ---",
            bpy.context.scene.objects.active,
        )        bpy.context.area.type = "VIEW_3D"
        bpy.ops.view3d.snap_cursor_to_selected()
        bpy.context.area.type = "TEXT_EDITOR"        boxInter2(DirectionInt)
        print("___9___The bounding box is", bpy.context.scene.objects.active)        if (len(bpy.context.object.data.polygons)) != 12 or (
            len(bpy.context.object.data.edges)
        ) != 24:
            edgeAnalysisForWing()
            x1 = coorObjectMode[-1][
                0
            ]  # The x coordinate of the last bounding box in object mode
            x2 = coorEditMode[-1][
                0
            ]  # The x coordinate of the last bounding box in edit mode
            y1 = coorObjectMode[-1][
                1
            ]  # The y coordinate of the last bounding box in object mode
            y2 = coorEditMode[-1][
                1
            ]  # The y coordinate of the last bounding box in object mode
            print(
                "___10___the coordinates are: x1=", x1, "x2=", x2, "y1=", y1, "y2=", y2
            )
            if (len(bpy.context.object.data.polygons)) == 6 or (
                len(bpy.context.object.data.edges)
            ) == 12:  # the module is totally off the base plane. Stop the wing() function.
                iterationCount.append(m)
                print(
                    "\n\t\t\t\t\t after",
                    m + 1,
                    "iterations, the operation has reached abbys, the last direction was",
                    DirectionInt,
                    "\n",
                )
                break
            elif (
                (lastDirection[-1] == "SW" or lastDirection[-1] == "SE")
                and (y1 > (y2 + 0.0001))
                and edgeAnalysisList[-1] == "Hor"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            elif (
                (lastDirection[-1] == "NW" or lastDirection[-1] == "NE")
                and (y1 < (y2 - 0.0001))
                and edgeAnalysisList[-1] == "Hor"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            elif (
                (lastDirection[-1] == "NE" or lastDirection[-1] == "SE")
                and (x1 < (x2 + 0.0001))
                and edgeAnalysisList[-1] == "Ver"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            elif (
                (lastDirection[-1] == "NW" or lastDirection[-1] == "SW")
                and (x1 > (x2 - 0.0001))
                and edgeAnalysisList[-1] == "Ver"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            else:  # The wing has reached an edge from the inside. wing() will be stopped.
                iterationCount.append(m)
                print(
                    "\n\t\t\t\t\t after",
                    m + 1,
                    "iterations, the operation reached the edge, the last direction was",
                    DirectionInt,
                    "\n",
                )
                break        else:
            if m == x:
                iterationCount.append(m)
                print(
                    "\n\t\t\t\t\t after",
                    m + 1,
                    "iterations, the operation reached the abbys, the last direction was",
                    DirectionInt,
                    "\n",
                )
                break
            else:
                nextIteration()
                m = m + 1def moveToLayer19():
    bpy.ops.object.move_to_layer(
        layers=(
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            True,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[19] = FalseA FURTHER ANALYSIS OF INTERSECTION BETWEEN BOUNDING BOX AND BASE PLANE
This time, it choses which elbow to place after the last wing, according to the last direction, the orientation of the edge, and therefore the next direction
def edgeAnalysis():
    A = bpy.context.object.location  # geometric coordinates of the center of the box
    bpy.ops.object.editmode_toggle()  # going into edit mode, which moves the center of gravity
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()  # moving cursor to new center of the object.
    bpy.context.area.type = "TEXT_EDITOR"
    B = bpy.context.scene.cursor_location  # retreiving the coordinates of the cursor
    bpy.ops.object.editmode_toggle()
    coorObjectMode.append(A)
    coorEditMode.append(B)
    print(A)
    print(B)
    A = [abs(i) for i in A]  # absolute value of coordinates
    B = [abs(i) for i in B]
    C = [b - a for a, b in zip(A, B)]  # Subtracting one vector from the other
    C = [abs(i) for i in C]
    print(C)
    if C[0] > C[1]:  # if the result in X is bigger than in Y, the edge is N/S
        print("___9___the edge is vertical")
    else:  # if the result is bigger in Y than in X, the edge is E/W
        print("___9___the edge is horizontal")Erasing modules from the end of the wing to make space for the elbow. The length of the wing determines how many modules will be erased, if at all.
    print(
        "___10___The box to be MOVED TO LAYER 20 is", bpy.context.scene.objects.active
    )
    moveToLayer19()
    if iterationCount[-1] >= 2:
        print("___11___the object to be deleted is", stack[-1])
        stack[-1].select = True
        bpy.ops.object.delete(use_global=False)
        stack.pop()  # removing the last module from the list, now we will have a new last module
        print("___12___the next object to be deleted is", stack[-1])
        stack[-1].select = True
        bpy.ops.object.delete(use_global=False)
        stack.pop()
        print("___13___after deleting, the last object is", stack[-1])
    elif iterationCount[-1] == 1:
        print("___14___the only object to be deleted is", stack[-1])
        stack[-1].select = True
        bpy.ops.object.delete(use_global=False)
        stack.pop()
    else:
        print("___15___NO deleting, the last object is STILL", stack[-1])
    stack[-1].select = True  # selecting the new last module
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    print("___14___The very last direction is", lastDirection[-1])
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"According to the last know direction and the direction of barrier edge, we can choose our elbow If the module reaches a corner, it is cut in two areas. In this case, the center of mass will move more, paradoxically, towards the side that was cut less(since there is suddenly a dense collection of vertices).Therefore, if the box is cut by a corner (which means that both x and y of the center of mass move), the displacement is transformed by ^-1 so that it reflects the more important edge
If the edge is Horizontal, the direction is flipped horizontally. If vertical, then vertically. Then, the next direction is appended to the list, as well as the chosen elbow
    if C[0] > 0.001 and C[1] > 0.001:
        print("\n\n*****************the elbow is on a corner**********************\n\n")
        if lastDirection[-1] == "NW" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("NE")
            elbow.append("NW2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NW" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("SW")
            elbow.append("NW2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("NW")
            elbow.append("NE2NW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("SE")
            elbow.append("NE2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("SW")
            elbow.append("SE2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("NE")
            elbow.append("SE2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("SE")
            elbow.append("SW2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("NW")
            elbow.append("SW2NW")
            print("___16___the next direction is", nextDirection[-1])
    else:
        if lastDirection[-1] == "NW" and C[0] > C[1]:
            nextDirection.append("NE")
            elbow.append("NW2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NW" and C[0] < C[1]:
            nextDirection.append("SW")
            elbow.append("NW2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and C[0] > C[1]:
            nextDirection.append("NW")
            elbow.append("NE2NW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and C[0] < C[1]:
            nextDirection.append("SE")
            elbow.append("NE2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and C[0] > C[1]:
            nextDirection.append("SW")
            elbow.append("SE2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and C[0] < C[1]:
            nextDirection.append("NE")
            elbow.append("SE2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and C[0] > C[1]:
            nextDirection.append("SE")
            elbow.append("SW2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and C[0] < C[1]:
            nextDirection.append("NW")
            elbow.append("SW2NW")
            print("___16___the next direction is", nextDirection[-1])
    print("___17___the elbow is therefore", elbow[-1])FOLLOWING THE CHOICE OF THE ELBOW IN edgeAnalysis(), THE ELBOW IS PLACED AND THE CENTER IS ADJUSTED FOR MIRRORING THE MODULE TO THE OTHER SIDE OF THE ELBOW, READY FOR THE NEXT WING
def elbowChoice(Dir):First, the proper elbow has to be copied from layer 3 or 4. If the last direction is parallel to the beginning direction, the modules will be moving forward and require a module from layer 3. If perpendicular, mirroring will cause modules to move backwards, in which case they will require the elbow from layer 4.
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    bpy.ops.object.select_all(action="TOGGLE")
    if Dir == "SW2NW_2" or Dir == "NE2SE_2" or Dir == "NW2SW_2" or Dir == "SE2NE_2":
        bpy.context.scene.layers[4] = True
    else:
        bpy.context.scene.layers[3] = True
    bpy.data.objects[Dir].select = True
    bpy.context.scene.objects.active = bpy.data.objects[Dir]
    print(
        "___18___The selected elbow before duplication is", bpy.context.selected_objects
    )
    bpy.ops.object.duplicate()
    placedElbow.append(bpy.context.scene.objects.active)
    print(
        "___19___The active elbow is",
        bpy.context.scene.objects.active,
        "the last in the list is",
        placedElbow[-1],
    )
    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    if Dir == "SW2NW_2" or Dir == "SE2NE_2" or Dir == "NW2SW_2" or Dir == "SE2NE_2":
        bpy.context.scene.layers[4] = False
    else:
        bpy.context.scene.layers[3] = FalseAccording to the chosen elbow, the cursor (and then the origin) has to be displaced differently so that the modules mirror to the same position on the other side.
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
    if Dir == "NW2SW" or Dir == "SW2NW":
        bpy.context.scene.cursor_location += Vector((-0.10706, 0, 0))
    elif Dir == "NE2SE" or Dir == "SE2NE":
        bpy.context.scene.cursor_location += Vector((0.10706, 0, 0))
    elif Dir == "NW2SW_2" or Dir == "SW2NW_2":
        bpy.context.scene.cursor_location += Vector((-0.28859, 0, 0))
    elif Dir == "NE2SE_2" or Dir == "SE2NE_2":
        bpy.context.scene.cursor_location += Vector((0.28859, 0, 0))
    bpy.context.area.type = "TEXT_EDITOR"
    bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    scene = bpy.context.scene
    for ob in scene.objects:
        ob.select = False    stack[-1].select = True
    print(
        "___20___the selected objects after placing elbow is",
        bpy.context.selected_objects,
    )
    bpy.context.scene.objects.active = stack[-1]
    print(
        "___21___the active object after elbowchoice is",
        bpy.context.scene.objects.active,
    )NOW THAT THE ELBOW HAS BEEN PREPARED AND THE CURSOR HAS BEEN MOVED TO ITS CENTER, THE NEXT WING CAN BE CREATED.
def nextWing():
    print(
        "___22___the elbow after executing NEXTWING is", elbow[-1]
    )  # checking that the lists are behaving like they should
    print("___23___the stack after executing NEXTWING is", stack)According to the last elbow and the initial direction, the last module will be mirrored and a new wing will start in the next direction. Modules continuing North or South after bouncing off of a N-S edge will simply be mirrored around their respective extremity. Modules changing from N to S or inversely will be mirrored around their new origin, which is placed in the cursor, which was previously placed in the center of the elbow. The origin is then restored to the habitual place in the new module, by reapplying the same displacement as before.
    if (
        elbow[-1] == "NW2NE"
    ):  # here, for example, the module is flipped along the x axis, with no exterior elbow.
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_______The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_______The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        if startingPos == 1 or startingPos == 3:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        else:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
        stack.append(bpy.context.scene.objects.active)
        wing("NE", 0.16, 0.16, 1)
    elif (
        elbow[-1] == "NW2SW"
    ):  # here it is flipped along the Y axis, so an elbow is added.
        if startingPos == 0 or startingPos == 2:
            elbowChoice("NW2SW")
        else:
            elbowChoice("NW2SW_2")
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "_____24______The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "_____25______The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )
        bpy.context.area.type = "VIEW_3D"
        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((-0.10706, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((-0.28859, 0, 0))
        bpy.context.area.type = "TEXT_EDITOR"
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("SW", -0.16, -0.16, 3)
    elif elbow[-1] == "NE2NW":
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "_____24______The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "_____25______The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        if startingPos == 0 or startingPos == 2:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        else:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
        print(
            "___24___the first active object in next wing is",
            bpy.context.scene.objects.active,
        )
        stack.append(bpy.context.scene.objects.active)
        wing("NW", -0.16, 0.16, 0)
    elif elbow[-1] == "NE2SE":
        if startingPos == 1 or startingPos == 3:
            elbowChoice("NE2SE")
        else:
            elbowChoice("NE2SE_2")
        bpy.ops.object.select_all(action="TOGGLE")
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The SELECTED objects AFTER duplicating are_____________",
            bpy.context.selected_objects,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )
        bpy.context.area.type = "VIEW_3D"
        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((0.28859, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((0.10706, 0, 0))
        bpy.context.area.type = "TEXT_EDITOR"
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("SE", 0.16, -0.16, 2)
    elif elbow[-1] == "SE2SW":
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        if startingPos == 0 or startingPos == 2:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
        else:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        print(
            "___24___the first active object in next wing is",
            bpy.context.scene.objects.active,
        )
        stack.append(bpy.context.scene.objects.active)
        wing("SW", -0.16, -0.16, 3)
    elif elbow[-1] == "SE2NE":
        if startingPos == 0 or startingPos == 2:
            elbowChoice("SE2NE")
        else:
            elbowChoice("SE2NE_2")
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )
        print(
            "___26___the CURSOR position BEFORE moving it is",
            bpy.context.scene.cursor_location,
        )
        bpy.context.area.type = "VIEW_3D"
        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((0.10706, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((0.28859, 0, 0))
        bpy.context.area.type = "TEXT_EDITOR"
        print(
            "___27___the CURSOR position AFTER moving it is",
            bpy.context.scene.cursor_location,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("NE", 0.16, 0.16, 1)
    elif elbow[-1] == "SW2SE":
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        if startingPos == 0 or startingPos == 2:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
        else:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        print(
            "___24___the first active object in next wing is",
            bpy.context.scene.objects.active,
        )
        stack.append(bpy.context.scene.objects.active)
        wing("SE", 0.16, -0.16, 2)
    elif elbow[-1] == "SW2NW":
        if startingPos == 1 or startingPos == 3:
            elbowChoice("SW2NW")
        else:
            elbowChoice("SW2NW_2")
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )
        bpy.context.area.type = "VIEW_3D"
        bpy.ops.view3d.snap_cursor_to_selected()
        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((-0.28859, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((-0.10706, 0, 0))
        bpy.context.area.type = "TEXT_EDITOR"
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("NW", -0.16, 0.16, 0)AFTER THE FINAL WING, THE END PIECE, IDENTICAL TO THE START PIECE, MUST BE ORIENTATED AND PLACED. THIS IS DONE ACCORDING TO THE DIRECTION OF THE LAST MODULE AND ITS TRAVEL ORIENTATION.
def end():
    endPiece = []
    if nextDirection[-1] == "NW":
        if nextDirection[0] in ("NW", "SE"):
            endName = "endNW"
        else:
            endName = "endSE"
    elif nextDirection[-1] == "NE":
        if nextDirection[0] in ("NE", "SW"):
            endName = "endNE"
        else:
            endName = "endSE"
    elif nextDirection[-1] == "SW":
        if nextDirection[0] in ("SW", "NE"):
            endName = "endSW"
        else:
            endName = "endNE"
    elif nextDirection[-1] == "SE":
        if nextDirection[0] in ("SE", "NW"):
            endName = "endSE"
        else:
            endName = "endNW"
    scene = bpy.context.scene
    bpy.ops.object.select_all(action="TOGGLE")
    bpy.context.scene.layers[5] = True
    object = bpy.data.objects[endName]
    object.select = True
    bpy.context.scene.objects.active = bpy.data.objects[endName]
    bpy.ops.object.duplicate()
    endPiece.append(bpy.context.scene.objects.active)
    print("_____THE ENDPIECE IS_____", endPiece[-1])
    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[5] = False
    bpy.context.scene.layers[0] = True
    for ob in scene.objects:
        ob.select = False
    stack[-1].select = True
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    for ob in scene.objects:
        ob.select = False
    endPiece[-1].select = True
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
    bpy.context.area.type = "TEXT_EDITOR"THE SEQUENCE IS STARTED WITH A RANDOM INTEGER, FROM 0 TO 3, DENOTING THE STARTING DIRECTION
startingPos = random.randint(0, 3)  # a random initial direction
print(
    "\n\n\n\n\n\n start \n\n\n\n\n\n"
)  # This is printed to seperate from previous outputs while debugging
if startingPos == 0:
    nextDirection.append("NW")
    print("the starting direction is", "NW")
elif startingPos == 1:
    nextDirection.append("NE")
    print("the starting direction is", "NE")
elif startingPos == 2:
    nextDirection.append("SE")
    print("the starting direction is", "SE")
elif startingPos == 3:
    nextDirection.append("SW")
    print("the starting direction is", "SW")
z = 8  # This counts the number of wings in the sequence
if startingPos == 0:
    START(
        "DEB1", "MOD1", "NW", -0.16, 0.16
    )  # places the starting module and the first directional module
    wing("NW", -0.16, 0.16, 0)  # builds the first wing in the randomly chosen direction
    x = 0
    while x < z:
        edgeAnalysis()  # analyses the edge when the first wing stops
        nextWing()  # builds the next wing
        x = x + 1
    moveToLayer19()  # after all wings are done, the last remaining bounding box is moved to layer 19
elif startingPos == 1:
    START("DEB2", "MOD2", "NE", 0.16, 0.16)
    wing("NE", 0.16, 0.16, 1)
    x = 0
    while x < z:
        edgeAnalysis()
        nextWing()
        x = x + 1
    moveToLayer19()
elif startingPos == 2:
    START("DEB3", "MOD3", "SE", 0.16, -0.16)
    wing("SE", 0.16, -0.16, 2)
    x = 0
    while x < z:
        edgeAnalysis()
        nextWing()
        x = x + 1
    moveToLayer19()
elif startingPos == 3:
    START("DEB4", "MOD4", "SW", -0.16, -0.16)
    wing("SW", -0.16, -0.16, 3)
    x = 0
    while x < z:
        edgeAnalysis()
        nextWing()
        x = x + 1
    moveToLayer19()Finally, some modules are removed, according to the length of the last wing, so as to make place for the end module Then, the end moduled is position in place.
x = iterationCount[-1] + 1
if x < 4:
    y = 2
else:
    y = 3
while y > 0:
    stack[-1].select = True
    bpy.ops.object.delete(use_global=False)
    stack.pop()
    y = y - 1
end()
os.system(
    "cls"
)  # This clears all output from the system console. To disable, place hash tag in the beginning of this line.
a = len(stack) + 1
b = z
c = nextDirection[0]
d = nextDirection[-1]
print("\n\n\n")
print(
    "            ########################################################################################################################"
)
print(
    "            #                                                                                                                      #"
)
print(
    "                                                     HABITAT 67' - BOUND DEVELOPMENT                                                "
)
print(
    "            #                                                                                                                      #"
)
print(
    "                              This conjugation of the Habitat 67' vocabulary has",
    a,
    "modules, in",
    b,
    "wings                          ",
)
print(
    "            #                                                                                                                      #"
)
print(
    "                                       The first direction was",
    c,
    "and the last direction was",
    d,
    "                                  ",
)
print(
    "            #                                                                                                                      #"
)
print(
    "            ########################################################################################################################"
)